<?php
	class html_filter{
		private $tags = array();
		private $del_tags = FALSE;
		function set_tags($arr=array()){
			if(!is_array($arr)) $arr=array();
			foreach($arr as $key=>$value){
				for($i=0; $i<count($value); $i++){
					$arr[$key][$i] = strtolower($arr[$key][$i]);
				}
			}
			$this->tags = array_change_key_case($arr);
		}
		function del_invalid_tags($value){
			if($value) $this->del_tags = TRUE;
		}
		function filter($html_code){
			$html_code = stripslashes($html_code);
			$open_tags_stack = array();
			$code = FALSE;
			$seg = array();
			while(preg_match('/<[^<>]+>/si', $html_code, $matches, PREG_OFFSET_CAPTURE)){
				if($matches[0][1]) $seg[] = array('seg_type'=>'text', 'value'=>substr($html_code, 0, $matches[0][1]));	
				$seg[] = array('seg_type'=>'tag', 'value'=>$matches[0][0]);
				$html_code = substr($html_code, $matches[0][1]+strlen($matches[0][0]));
			}
			if($html_code != '') $seg[] = array('seg_type'=>'text', 'value'=>$html_code);
			for($i=0; $i<count($seg); $i++){
				if($seg[$i]['seg_type'] == 'text'){ 
				//$seg[$i]['value'] = htmlentities($seg[$i]['value'], ENT_QUOTES, 'UTF-8');
				}elseif($seg[$i]['seg_type'] == 'tag'){
					preg_match('#^<\s*(/)?\s*([a-z0-9]+)(.*?)>$#si', $seg[$i]['value'], $matches);
					@$matches[1] ? @$seg[$i]['tag_type']='close' : @$seg[$i]['tag_type']='open';
					@$seg[$i]['tag_name'] = @strtolower($matches[2]);
					if(($seg[$i]['tag_name']=='code') && ($seg[$i]['tag_type']=='close')) $code = FALSE;
					
					if($code) {
						$seg[$i]['seg_type'] = 'text';
						$i--;
						continue;
					}
					if($seg[$i]['tag_type'] == 'open') {
						if(!array_key_exists($seg[$i]['tag_name'], $this->tags)){
							if($this->del_tags) $seg[$i]['action'] = 'del';	
							else {$seg[$i]['seg_type'] = 'text';
								$i--;
								continue;
							}	
						}
						else {
							preg_match_all('#([a-z]+)\s*=\s*([\'\"])\s*(.*?)\s*\2#si', $matches[3], $attr_m, PREG_SET_ORDER);
							$attr = array();
							foreach($attr_m as $arr) {
								if(in_array(strtolower($arr[1]), $this->tags[$seg[$i]['tag_name']])) $attr[strtolower($arr[1])] = $arr[3];
							}
							$seg[$i]['attr'] = $attr;
							
							if($seg[$i]['tag_name'] == 'code') $code = TRUE;
							
							if(!count($this->tags[$seg[$i]['tag_name']]) || ($this->tags[$seg[$i]['tag_name']][count($this->tags[$seg[$i]['tag_name']])-1] != FALSE)) array_push($open_tags_stack, $seg[$i]['tag_name']);
						}
					}
					else {
						if(array_key_exists($seg[$i]['tag_name'], $this->tags) && (!count($this->tags[$seg[$i]['tag_name']]) || ($this->tags[$seg[$i]['tag_name']][count($this->tags[$seg[$i]['tag_name']])-1] != FALSE))){
							
							if($seg[$i]['tag_name'] == 'code') $code = FALSE;
							
							if((count($open_tags_stack) == 0) || (!in_array($seg[$i]['tag_name'], $open_tags_stack))) {
								if($this->del_tags) $seg[$i]['action'] = 'del';	
								else {$seg[$i]['seg_type'] = 'text';
									$i--;
									continue;
								}
							}
							else {
								$tn = array_pop($open_tags_stack);
								if($seg[$i]['tag_name'] != $tn){
									array_splice($seg, $i, 0, array(array('seg_type'=>'tag', 'tag_type'=>'close', 'tag_name'=>$tn, 'action'=>'add')));	
								}	
							}
						}
						else {
							if($this->del_tags) $seg[$i]['action'] = 'del';	
							else {$seg[$i]['seg_type'] = 'text';
								$i--;
								continue;
							}
						}
					}
				}
			} 
			foreach(array_reverse($open_tags_stack) as $value) {
				array_push($seg, array('seg_type'=>'tag', 'tag_type'=>'close', 'tag_name'=>$value, 'action'=>'add'));
			}
			$filtered_HTML = '';
			foreach($seg as $segment) {
				if($segment['seg_type'] == 'text') $filtered_HTML .= $segment['value'];
				
				elseif(($segment['seg_type'] == 'tag') && (@$segment['action'] != 'del')) {
					if($segment['tag_type'] == 'open') {
						$filtered_HTML .= '<'.$segment['tag_name'];
						if(is_array($segment['attr'])){
							foreach($segment['attr'] as $attr_key=>$attr_val){
								$filtered_HTML .= ' '.$attr_key.'="'.$attr_val.'"';	
							}
						}
						if (count($this->tags[$segment['tag_name']]) && ($this->tags[$segment['tag_name']][count($this->tags[$segment['tag_name']])-1] == FALSE)) $filtered_HTML .= " /";
						$filtered_HTML .= '>';
					}
					elseif($segment['tag_type'] == 'close'){
						$filtered_HTML .= '</'.$segment['tag_name'].'>';
					}
				}
			}
			
			return $filtered_HTML;
		}			
	};